package tr.edu.metu.is.mega.client.user;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import tr.edu.metu.is.mega.client.core.DialogCallbackAdapter;
import tr.edu.metu.is.mega.client.core.MainWindow;
import tr.edu.metu.is.mega.client.resource.MEGAResources;
import tr.edu.metu.is.mega.client.service.GwtUserService;
import tr.edu.metu.is.mega.client.service.GwtUserServiceAsync;
import tr.edu.metu.is.mega.common.dto.UserDTO;

import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.Style.SortDir;
import com.extjs.gxt.ui.client.data.BasePagingLoadConfig;
import com.extjs.gxt.ui.client.data.BasePagingLoader;
import com.extjs.gxt.ui.client.data.BeanModel;
import com.extjs.gxt.ui.client.data.BeanModelFactory;
import com.extjs.gxt.ui.client.data.BeanModelLookup;
import com.extjs.gxt.ui.client.data.BeanModelReader;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.PagingLoadConfig;
import com.extjs.gxt.ui.client.data.PagingLoadResult;
import com.extjs.gxt.ui.client.data.PagingLoader;
import com.extjs.gxt.ui.client.data.RpcProxy;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.store.Store;
import com.extjs.gxt.ui.client.store.StoreSorter;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.form.LabelField;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayout;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayout.HBoxLayoutAlign;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AbstractImagePrototype;

/**
 * Panel for managing users.
 */
public class UserManagementWindow extends MainWindow {

    private ToolBar toolBar;

    private Grid<BeanModel> grid;

    private ListStore<BeanModel> store;

    private final GwtUserServiceAsync service;

    private PagingToolBar pagingBar;

    private PagingLoader<PagingLoadResult<ModelData>> loader;

    private TextField<String> textFieldFilter;

    private LabelField labelFilter;

    private LayoutContainer panelFilter;

    private LayoutContainer panelToolbarContainer;

    private Button buttonFilter;

    private Menu popupMenuGrid;

    private MenuItem menuItemUpdateUser;

    private final DateTimeFormat dateFormat;

    private MenuItem menuItemDeleteUser;

    private final BeanModelFactory modelFactoryUser;

    /**
     * Default constructor.
     */
    public UserManagementWindow() {
        super("UserManagementWindow", null);
        service = GwtUserService.Util.getInstance();
        modelFactoryUser = BeanModelLookup.get().getFactory(UserDTO.class);
        dateFormat = DateTimeFormat.getFormat("dd.MM.yyyy");
        setHeading(MEGAResources.RESOURCEBUNDLE.usermanagementwindow_heading());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void onRender(Element parent, int pos) {
        super.onRender(parent, pos);
        this.setLayout(new BorderLayout());

        BorderLayoutData layoutDataToolbar = new BorderLayoutData(LayoutRegion.NORTH);
        layoutDataToolbar.setCollapsible(false);
        layoutDataToolbar.setSplit(false);
        layoutDataToolbar.setSize(26);
        layoutDataToolbar.setMargins(new Margins(0));
        this.add(getPanelToolbarContainer(), layoutDataToolbar);

        BorderLayoutData layoutDataCenter = new BorderLayoutData(LayoutRegion.CENTER);
        layoutDataCenter.setMargins(new Margins(0));
        this.add(getGrid(), layoutDataCenter);

        BorderLayoutData layoutDataPagingbar = new BorderLayoutData(LayoutRegion.SOUTH);
        layoutDataPagingbar.setCollapsible(false);
        layoutDataPagingbar.setSplit(false);
        layoutDataPagingbar.setSize(25);
        layoutDataPagingbar.setMargins(new Margins(0));
        this.add(pagingBar, layoutDataPagingbar);
    }

    /**
     * Initializes and returns toolbar container.
     * 
     * @return toolbar container
     */
    private LayoutContainer getPanelToolbarContainer() {
        if (panelToolbarContainer == null) {
            panelToolbarContainer = new LayoutContainer(new BorderLayout());
            BorderLayoutData layoutDataCenter = new BorderLayoutData(LayoutRegion.CENTER);
            layoutDataCenter.setCollapsible(false);
            layoutDataCenter.setSplit(false);
            layoutDataCenter.setMargins(new Margins(0));
            panelToolbarContainer.add(getToolBar(), layoutDataCenter);

            BorderLayoutData layoutDataFilter = new BorderLayoutData(LayoutRegion.EAST);
            layoutDataFilter.setCollapsible(false);
            layoutDataFilter.setSplit(false);
            layoutDataFilter.setSize(210);
            layoutDataFilter.setMargins(new Margins(0));
            panelToolbarContainer.add(getPanelFilter(), layoutDataFilter);
        }
        return panelToolbarContainer;
    }

    /**
     * Initializes and returns toolbar.
     * 
     * @return toolbar
     */
    private ToolBar getToolBar() {
        if (toolBar == null) {
            toolBar = new ToolBar();
            toolBar.add(getAddButton());
        }
        return toolBar;
    }

    private Button getAddButton() {
        Button addButton = new Button("Add", AbstractImagePrototype.create(MEGAResources.ICONBUNDLE.add()));
        addButton.setToolTip(MEGAResources.RESOURCEBUNDLE.add());
        addButton.addSelectionListener(new SelectionListener<ButtonEvent>() {

            @Override
            public void componentSelected(ButtonEvent ce) {
                UserDTO user = new UserDTO();
                UserEditDialog dialog = new UserEditDialog();

                dialog.create(user, new DialogCallbackAdapter<UserDTO>() {

                    @Override
                    public void onOk(UserDTO param) {
                        service.createUser(param, new AsyncCallback<UserDTO>() {

                            @Override
                            public void onFailure(Throwable caught) {
                                MessageBox.alert("Error", "Failed to update user information", null);
                            }

                            @Override
                            public void onSuccess(UserDTO result) {
                                grid.getStore().add(modelFactoryUser.createModel(result));
                                grid.getView().refresh(true);
                            }

                        });

                    }
                });
            }
        });
        return addButton;
    }

    /**
     * Initializes and returns textFieldFilter.
     * 
     * @return textFieldFilter
     */
    private TextField<String> getTextFieldFilter() {
        if (textFieldFilter == null) {
            textFieldFilter = new TextField<String>();
            textFieldFilter.addListener(Events.OnKeyPress, new Listener<FieldEvent>() {

                @Override
                public void handleEvent(FieldEvent be) {
                    if (be.getKeyCode() == KeyCodes.KEY_ENTER) {
                        loader.load();
                    }
                }
            });
        }
        return textFieldFilter;
    }

    /**
     * Initializes and returns labelFilter.
     * 
     * @return labelFilter
     */
    private LabelField getLabelFilter() {
        if (labelFilter == null) {
            labelFilter = new LabelField(MEGAResources.RESOURCEBUNDLE.filter());
        }
        return labelFilter;
    }

    /**
     * Initializes and returns panel filter.
     * 
     * @return panel filter
     */
    private LayoutContainer getPanelFilter() {
        if (panelFilter == null) {
            HBoxLayout layout = new HBoxLayout();
            layout.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);
            panelFilter = new LayoutContainer(layout);

            HBoxLayoutData layoutDataLabel = new HBoxLayoutData(2, 2, 2, 2);
            panelFilter.add(getLabelFilter(), layoutDataLabel);

            HBoxLayoutData layoutDataTextField = new HBoxLayoutData(2, 2, 2, 2);
            panelFilter.add(getTextFieldFilter(), layoutDataTextField);

            HBoxLayoutData layoutDataButton = new HBoxLayoutData(2, 2, 2, 2);
            panelFilter.add(getButtonFilter(), layoutDataButton);

        }
        return panelFilter;
    }

    /**
     * Initializes and returns button filter.
     * 
     * @return button filter
     */
    private Button getButtonFilter() {
        if (buttonFilter == null) {
            buttonFilter = new Button("");
            buttonFilter.setHeight(16);
            buttonFilter.setIcon(AbstractImagePrototype.create(MEGAResources.ICONBUNDLE.filter()));
            buttonFilter.addListener(Events.OnClick, new Listener<ButtonEvent>() {

                @Override
                public void handleEvent(ButtonEvent be) {
                    loader.load();
                }
            });
        }
        return buttonFilter;
    }

    /**
     * Initilizes and returns grid.
     * 
     * @return grid
     */
    private Grid<BeanModel> getGrid() {
        if (grid == null) {
            RpcProxy<PagingLoadResult<UserDTO>> proxy = new RpcProxy<PagingLoadResult<UserDTO>>() {

                @Override
                protected void load(Object loadConfig, AsyncCallback<PagingLoadResult<UserDTO>> callback) {
                    PagingLoadConfig plc = (PagingLoadConfig) loadConfig;
                    String filter = textFieldFilter.getValue();
                    UserDTO user = new UserDTO();
                    user.setUserName(filter);
                    service.getUsers(plc, user, callback);
                }
            };
            BeanModelReader reader = new BeanModelReader();
            loader = new BasePagingLoader<PagingLoadResult<ModelData>>(proxy, reader);
            loader.setRemoteSort(true);
            store = new ListStore<BeanModel>(loader);

            pagingBar = new PagingToolBar(30);
            pagingBar.bind(loader);

            store.setStoreSorter(getSorter());
            List<ColumnConfig> columns = new ArrayList<ColumnConfig>(3);

            // user id column
            ColumnConfig column = new ColumnConfig(UserDTO.FIELD_USERNAME, MEGAResources.RESOURCEBUNDLE.userName(), 150);
            column.setRowHeader(true);
            column.setRenderer(new GridCellRenderer<BeanModel>() {

                @Override
                public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<BeanModel> store,
                        Grid<BeanModel> grid) {
                    return ((UserDTO) model.getBean()).getUserName();
                }
            });
            columns.add(column);

            columns.add(new ColumnConfig(UserDTO.FIELD_ROLE, MEGAResources.RESOURCEBUNDLE.usermanagementwindow_role(), 150));

            // name column
            column = new ColumnConfig(UserDTO.FIELD_NAME, MEGAResources.RESOURCEBUNDLE.name(), 100);
            column.setRenderer(new GridCellRenderer<BeanModel>() {

                @Override
                public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<BeanModel> store,
                        Grid<BeanModel> grid) {
                    return ((UserDTO) model.getBean()).getName();
                }
            });
            columns.add(column);

            // surname column
            column = new ColumnConfig(UserDTO.FIELD_SURNAME, MEGAResources.RESOURCEBUNDLE.surname(), 100);
            column.setRenderer(new GridCellRenderer<BeanModel>() {

                @Override
                public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<BeanModel> store,
                        Grid<BeanModel> grid) {
                    return ((UserDTO) model.getBean()).getSurname();
                }
            });
            columns.add(column);

            // e-mail column
            column = new ColumnConfig(UserDTO.FIELD_EMAIL, MEGAResources.RESOURCEBUNDLE.email(), 150);
            column.setRenderer(new GridCellRenderer<BeanModel>() {

                @Override
                public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<BeanModel> store,
                        Grid<BeanModel> grid) {
                    return ((UserDTO) model.getBean()).getEmail();
                }
            });
            columns.add(column);

            // expire date column
            column = new ColumnConfig(UserDTO.FIELD_EXPIRYDATE, MEGAResources.RESOURCEBUNDLE.expiredate(), 150);
            column.setRenderer(new GridCellRenderer<BeanModel>() {

                @Override
                public Object render(BeanModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<BeanModel> store,
                        Grid<BeanModel> grid) {
                    UserDTO user = (UserDTO) model.getBean();
                    if (user.getExpiryDate() != null) {
                        return dateFormat.format(user.getExpiryDate());
                    } else {
                        return "";
                    }
                }
            });
            columns.add(column);

            ColumnModel columnModel = new ColumnModel(columns) {

                @Override
                public boolean isCellEditable(int colIndex) {
                    return false;
                }
            };
            grid = new Grid<BeanModel>(store, columnModel);
            grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
            grid.setAutoExpandColumn("email");
            grid.setLoadMask(true);
            grid.setStateId("userGrid");
            grid.setStateful(true);
            grid.addListener(Events.Attach, new Listener<GridEvent<BeanModel>>() {

                @Override
                public void handleEvent(GridEvent<BeanModel> be) {
                    PagingLoadConfig config = new BasePagingLoadConfig();
                    config.setOffset(0);
                    config.setLimit(20);
                    Map<String, Object> state = grid.getState();
                    if (state.containsKey("offset")) {
                        config.setOffset((Integer) state.get("offset"));
                        config.setLimit((Integer) state.get("limit"));
                    }
                    if (state.containsKey("sortField")) {
                        config.setSortField((String) state.get("sortField"));
                        config.setSortDir(SortDir.valueOf((String) state.get("sortDir")));
                    }
                    loader.load(config);
                }
            });
            grid.setContextMenu(getGridContextMenu());
        }
        return grid;
    }

    /**
     * Initializes and returns grid context menu.
     * 
     * @return grid context menu
     */
    private Menu getGridContextMenu() {
        if (popupMenuGrid == null) {
            popupMenuGrid = new Menu();
            popupMenuGrid.add(getMenuItemUpdateUser());
            popupMenuGrid.add(getMenuItemDeleteUser());
            popupMenuGrid.addListener(Events.Show, new Listener<ComponentEvent>() {

                @Override
                public void handleEvent(ComponentEvent ce) {
                    if (grid.getSelectionModel().getSelectedItems().size() != 1) {
                        popupMenuGrid.setVisible(false);
                    }
                }
            });
        }
        return popupMenuGrid;
    }

    /**
     * Initiates and returns update menu item.
     * 
     * @return update menu item
     */
    private MenuItem getMenuItemUpdateUser() {
        if (menuItemUpdateUser == null) {
            menuItemUpdateUser = new MenuItem(MEGAResources.RESOURCEBUNDLE.update());
            menuItemUpdateUser.addSelectionListener(getUpdateSelectionListener());
        }
        return menuItemUpdateUser;
    }

    private SelectionListener<MenuEvent> getUpdateSelectionListener() {
        return new SelectionListener<MenuEvent>() {

            @Override
            public void componentSelected(MenuEvent ce) {
                UserDTO user = (UserDTO) grid.getSelectionModel().getSelectedItem().getBean();
                UserEditDialog dialog = new UserEditDialog();
                dialog.update(user, new DialogCallbackAdapter<UserDTO>() {

                    @Override
                    public void onOk(UserDTO user) {
                        service.updateUser(user, new AsyncCallback<UserDTO>() {

                            @Override
                            public void onFailure(Throwable caught) {
                                MessageBox.alert("Error", "Failed to update user information", null);
                            }

                            @Override
                            public void onSuccess(UserDTO result) {
                                grid.getStore().update(modelFactoryUser.createModel(result));
                                grid.getView().refresh(true);
                            }
                        });
                    }
                });
            }
        };
    }

    /**
     * Initiates and returns delete menu item.
     * 
     * @return delete menu item
     */
    private MenuItem getMenuItemDeleteUser() {
        if (menuItemDeleteUser == null) {
            menuItemDeleteUser = new MenuItem(MEGAResources.RESOURCEBUNDLE.delete());
            menuItemDeleteUser.addSelectionListener(getDeleteSelectionListener());
        }
        return menuItemDeleteUser;
    }

    private SelectionListener<MenuEvent> getDeleteSelectionListener() {
        return new SelectionListener<MenuEvent>() {

            @Override
            public void componentSelected(MenuEvent ce) {
                final UserDTO user = (UserDTO) grid.getSelectionModel().getSelectedItem().getBean();
                service.deleteUser(user, new AsyncCallback<Void>() {

                    @Override
                    public void onSuccess(Void result) {
                        Info.display("", "User deleted");
                        grid.getStore().remove(modelFactoryUser.createModel(user));
                        grid.getView().refresh(true);
                    }

                    @Override
                    public void onFailure(Throwable caught) {
                        Info.display("", "User delete failed");
                    }
                });
            }
        };
    }

    /**
     * Initializes and returns grid sorter.
     * 
     * @return grid sorter
     */
    private StoreSorter<BeanModel> getSorter() {
        StoreSorter<BeanModel> sorter = new StoreSorter<BeanModel>() {

            @Override
            public int compare(Store<BeanModel> store, BeanModel m1, BeanModel m2, String property) {
                UserDTO user1 = m1.getBean();
                UserDTO user2 = m2.getBean();
                if (property.equals(UserDTO.FIELD_USERNAME)) {
                    return user1.getUserName().compareTo(user2.getUserName());
                } else if (property.equals(UserDTO.FIELD_NAME)) {
                    return user1.getName().compareTo(user2.getName());
                } else if (property.equals(UserDTO.FIELD_SURNAME)) {
                    return user1.getSurname().compareTo(user2.getSurname());
                } else if (property.equals(UserDTO.FIELD_EMAIL)) {
                    return user1.getEmail().compareTo(user2.getEmail());
                } else if (property.equals(UserDTO.FIELD_EXPIRYDATE)) {
                    return user1.getExpiryDate().compareTo(user2.getExpiryDate());
                }
                return super.compare(store, m1, m2, property);
            }
        };
        return sorter;
    }

}
